#include "odbc_defines.h"
#include "utils.h"

static void unbindcols(stmt_t *s);
static void do_query(stmt_t *s);

extern SQLRETURN op_alloc_env(SQLHENV *env)
{
	env_t *e;

	if (env == NULL) {
		return SQL_INVALID_HANDLE;
	}

	e = (env_t *)malloc(sizeof (env_t));
	if (NULL == e) {
		*env = SQL_NULL_HENV;
		return SQL_ERROR;
	}

	/* magic value inited during allocating. */
	e->magic = ENV_MAGIC;

#if defined(_WIN32) || defined(_WIN64)
	InitializeCriticalSection(&e->cs);
	/* At the time, a new env variable has no owner.*/
	e->owner = 0;
#endif
	/* The same as the DB connection,although it hasn't alloc.*/
	e->dbcs = NULL;
	*env = (SQLHENV) e;
	return SQL_SUCCESS;
}

extern SQLRETURN op_alloc_connect(SQLHENV env, SQLHDBC *dbc)
{	
	dbc_t *d;
	env_t *e;

	if (dbc == NULL) {
		return SQL_ERROR;
	}

	d = (dbc_t *)malloc(sizeof(dbc_t));
	if (d == NULL) {
		*dbc = SQL_NULL_HDBC;
		return SQL_ERROR;
	}

	memset(d, 0, sizeof(dbc_t));

	e = (env_t *)env;

#if defined(_WIN32) || defined(_WIN64)
	/*e->magic's value has been set to ENV_MAGIC during env initializing.*/
	if (e->magic == ENV_MAGIC) { 
		EnterCriticalSection(&e->cs);
		e->owner = GetCurrentThreadId();
	}
#endif

	if (e->magic == ENV_MAGIC) {
		dbc_t *n, *p;

		/* Some DB connect attributes depends 
		* depends on env's.
		*/
		d->env = e;

		p = NULL;
		n = e->dbcs;

		/*	'n' is a dbc chain,here get the last 
		*	dbc and set to 'p'.
		*/
		while (n) {
			p = n;
			n = n->next;
		}

		/* The truth is 'p' point to e->dbcs,
		* if 'p' true, then set e->dbcs->next = d.
		* if 'p' false, set e->dbcs = d.Because 'p'
		* is the last DB connection on the chain.
		*/
		if (p) {
			p->next = d;
		}else {
			e->dbcs = d;
		}
	}

#if defined(_WIN32) || defined(_WIN64)
	if (e->magic == ENV_MAGIC) {
		e->owner = 0;	
		LeaveCriticalSection(&e->cs);
	}	/* At this point, no one own the env,j obs done and
		* leaving critical section.
		*/

#endif

	d->magic = DBC_MAGIC;
	*dbc = (SQLHDBC) d;
	return SQL_SUCCESS;
}

/*
 Allocate HSTMT to a given HDBC (driver internal version).
 @param dbc database connection handle
 @param stmt pointer to statement handle
 @result ODBC error code
 Note: Does the parse result set to the 'stmt' at this time?
*/
extern  SQLRETURN op_alloc_stmt(SQLHDBC dbc, SQLHSTMT *stmt)
{
	dbc_t *d;
	stmt_t *s, *sl, *pl;

	if (dbc == SQL_NULL_HDBC)
		return SQL_INVALID_HANDLE;

	d = (dbc_t *)dbc;
	if (d->magic != DBC_MAGIC || stmt == NULL)
		return SQL_INVALID_HANDLE;

	s = (stmt_t *)malloc(sizeof(stmt_t));
	if (s == NULL) {
		*stmt = SQL_NULL_HSTMT;
		return SQL_ERROR;
	}

	*stmt = (SQLHSTMT)s;
	memset(s, 0, sizeof(stmt_t));

	s->dbc = dbc;
	s->no_wchar[1] = 0;
	s->row_status0 = &s->row_status1;
	s->retr_data = SQL_RD_ON;
	s->bind_type = SQL_BIND_BY_COLUMN;
	s->bind_offs = NULL;
	s->paramset_size = 1;
	sl = d->stmt;
	pl = NULL;

	/* To get the last one */
	while(sl) {
		pl = sl;
		sl = sl->next;
	}

	if (pl) {
		pl->next = s;
	}
	else {
		d->stmt = s;
	}

	return SQL_SUCCESS;
}

/*
* Internal free HENV.
* @param env environment handle
* @result ODBC error code
*/
extern SQLRETURN op_free_env(SQLHENV env)
{
	env_t *e;

	if (env == SQL_NULL_HENV)
		return SQL_INVALID_HANDLE;

	e = (env_t *)env;
	if (e->magic != ENV_MAGIC)	
		return SQL_SUCCESS;		/* Why return at this point? Other resources haven't deallocate yet!	
						 * Because the ENV_MAGIC?
						 */		
#if defined(_WIN32) || defined(_WIN64)
	EnterCriticalSection(&e->cs);
	e->owner = GetCurrentThreadId();
#endif

	if (e->dbcs) {			/* A env may have many db connections.See dbc_t definition */
#if defined(_WIN32) || defined(_WIN64)
		LeaveCriticalSection(&e->cs);
		e->owner = 0;		
#endif
		return SQL_ERROR;	/* Why return error? Any error ocurred here? */
	}

	e->magic = DEAD_MAGIC;
#if defined(_WIN32) || defined(_WIN64)
	e->owner = 0;
	LeaveCriticalSection(&e->cs);
	DeleteCriticalSection(&e->cs);
#endif
	free(e);

	return SQL_SUCCESS;
}

/*
* Internal free connection (HDBC).
* @param dbc database connection handle
* @result ODBC error code
*/
extern SQLRETURN op_free_connect(SQLHDBC dbc)
{
	SQLRETURN ret = SQL_ERROR;
	dbc_t *d;
	env_t *e;

	if (dbc == SQL_NULL_HENV)
		return SQL_INVALID_HANDLE;

	d = (dbc_t *)dbc;
	if (d->magic != DBC_MAGIC)
		return SQL_INVALID_HANDLE;


	e = d->env;
	if (e && e->magic == ENV_MAGIC) {
#if defined(_WIN32) || defined(_WIN64)
		EnterCriticalSection(&e->cs);
		e->owner = GetCurrentThreadId();
#endif
	}else	{
		e = NULL;
	}

	if (d->db_name) {
		set_stat_dbc(d, -1, "not disconnected", "HY000");
		goto done;
	}

	while (d->stmt)
		free_stmt((HSTMT) d->stmt);

	if (e && e->magic == ENV_MAGIC) {
		dbc_t *n, *p;

		p = NULL;
		n = e->dbcs;    /* n = dbc->e->dbcs */

		while(n) {
			/* Set this ahead to prevent evaluate 
			* to itslef. When need to dealloc env's
			* dbc, this is critical.
			*/
			if (n == d)						
				break;

			p = n;
			n = n->next;
		}

		if (n) {
			if (p)
				p->next = d->next;
			else
				e->dbcs = d->next;
		}
	}

	d->magic = DEAD_MAGIC;

	free(d);	
	ret = SQL_SUCCESS;

done:
#if defined(_WIN32) || defined(_WIN64)
	if (e) {
		e->owner = 0;
		LeaveCriticalSection(&e->cs);
	}
#endif	
	return ret;
}

/*
 Internal function to perform certain kinds of free/close on stmt_t.
 @param stmt statement handle
 @param opt SQL_RESET_PARAMS, SQL_UNBIND, SQL_CLOSE, or SQL_DROP
 @result ODBC error code
*/
extern SQLRETURN op_free_stmt(SQLHSTMT stmt, SQLUSMALLINT opt)
{
	stmt_t *s;
	SQLRETURN ret = SQL_SUCCESS;
	SQLHDBC dbc;

	if (stmt == SQL_NULL_HSTMT)
		return SQL_INVALID_HANDLE;

	HSTMT_LOCK(stmt);
	s = (stmt_t *) stmt;
	dbc = s->dbc;
	switch (opt)
	{
	case SQL_RESET_PARAMS:
		free_params(s);
		break;
	case SQL_UNBIND:
		unbindcols(s);
		break;
	case SQL_CLOSE:
		stmt_end_if(s);		/* <Note> need code */
		free_result(s, 0);
		break;
	case SQL_DROP:
		stmt_end_if(s);		/* <Note> need code	*/
		ret = free_stmt(stmt);
		break;
	default:
		set_stat_stmt(s, -1, "unsupported option", "HYC00");
		ret = SQL_ERROR;
		break;
	}

	HDBC_UNLOCK(dbc);
	return ret;
}

extern SQLRETURN  op_set_connect_attr(SQLHDBC hdbc,
	       	SQLINTEGER attribute,
		SQLPOINTER attr_value,
	       	SQLINTEGER attr_value_length)
{
	dbc_t *d;

	if (hdbc == SQL_NULL_HDBC)
		return SQL_INVALID_HANDLE;

	d = (dbc_t *) hdbc;
	switch(attribute) {
		/* openPlant RDB do not support transaction now.
		* so the first case would/should never match. 
		*/
	case SQL_AUTOCOMMIT:
		if (attr_value_length == SQL_IS_INTEGER 
				|| attr_value_length == SQL_IS_UINTEGER) {
			goto doit;
		}

		if (attr_value && attr_value_length >= sizeof(SQLINTEGER)) {
doit:
			return SQL_SUCCESS;
		}
		set_stat_dbc(d,
			       	-1, 
				"inattr_valueid attr_valueue_lengthgth or pointer",
			       	"HY009");
		return SQL_ERROR;
	default:
		set_stat_dbc(d, -1, "option attr_valueue changed", "01S02");
		return SQL_SUCCESS_WITH_INFO;
	}

	return SQL_SUCCESS;
}

/*
 Internal query preparation used by SQLPrepare() and SQLExecDirect().
 @param stmt statement handle
 @param query query string
 @param queryLen length of query string or SQL_NTS
 @result ODBC error code
*/
extern SQLRETURN op_prepare(SQLHSTMT stmt, SQLCHAR *query, SQLINTEGER queryLen)
{
	stmt_t *s;
	dbc_t *d;
	char *errp = NULL;

	if (stmt == SQL_NULL_HSTMT) {
		return SQL_INVALID_HANDLE;
	}

	s = (stmt_t *)stmt;
	if (s->dbc == SQL_NULL_HDBC) {
noconn:
		set_stat_stmt(s, -1, "not connected", "HY000");
		return SQL_ERROR;
	}

	d = (dbc_t *)s->dbc;
	if (!d->db_name) {
		goto noconn;
	}

	if (s->query) {
		freep(&s->query);
	}

	s->query = (char *)query;
	s->query_len = queryLen;
	if (s->query_len <= 0 && s->query_len != SQL_NTS) {
		set_stat_stmt(s, -1, "the length of query string error", "HY090");
		/* We ignore HY090,just set 'query' length to 's->query_len' */
		s->query_len = sizeof (s->query);
	}

	return SQL_SUCCESS;
}

/*
Internal bind parameter on HSTMT.
@param stmt statement handle
@param pnum parameter number, starting at 1
@param iotype input/output type of parameter
@param buftype type of host variable
@param ptype
@param coldef
@param scale
@param data pointer to host variable
@param buflen length of host variable
@param len output length pointer
@result ODBC error code
<Note> need code
*/
extern SQLRETURN op_bind_param(SQLHSTMT stmt, SQLUSMALLINT pnum, SQLSMALLINT iotype,
		SQLSMALLINT buftype, SQLSMALLINT ptype, SQLUINTEGER coldef,
		SQLSMALLINT scale, SQLPOINTER data, SQLINTEGER buflen, SQLLEN *len)
{
	stmt_t *s;
	bind_param_t *p;

	if(stmt == SQL_NULL_HSTMT) 
	{
		return SQL_INVALID_HANDLE;
	}

	s = (stmt_t *) stmt;
	if (pnum == 0) 
	{	
		set_stat_stmt(s, 
				-1, 
				"invalid parameter", "07009");
		return SQL_ERROR;
	}

	if (len && *len < 0 
			&& *len > SQL_LEN_DATA_AT_EXEC_OFFSET 
			&& *len != SQL_NTS 
			&& *len != SQL_NULL_DATA) 
	{
		set_stat_stmt(s, -1, "invalid length reference", "HY009");
		return SQL_ERROR;		
	}

	if(!data && (!len ||
				(*len != SQL_NULL_DATA && *len > SQL_LEN_DATA_AT_EXEC_OFFSET))) 
	{
		set_stat_stmt(s, -1, "invalid buffer", "HY003");
		return SQL_ERROR;	 
	}

	--pnum;

	if (s->bindparms) 
	{
		if(pnum >= s->nbindparms)
		{
			bind_param_t *newparms;

			newparms = (bind_param_t *)realloc(s->bindparms, (pnum + 1) * sizeof(bind_param_t));
			if (!newparms)
			{
outofmem:
				return no_memory(s);		/* <Note> need code no_memory(). */
			}

			s->bindparms = newparms;
			memset(&s->bindparms[s->nbindparms], 0, (pnum + 1 - s->nbindparms) * sizeof(bind_param_t));
			s->nbindparms = pnum + 1;
		}
	}
	else 
	{
		int npar = max(10, pnum + 1);

		s->bindparms = (bind_param_t *)malloc(npar * sizeof(bind_param_t));
		if(!s->bindparms) 
		{
			goto outofmem;
		}

		memset(s->bindparms, 0, npar * sizeof(bind_param_t));
		s->nbindparms = npar;
	}

	p = &s->bindparms[pnum];
	p->coldef = coldef;
	p->scale = scale;
	p->max = buflen;
	p->inc = buflen;
	p->lenp = p->lenp0 = len;
	p->offs = 0;
	p->len = 0;
	p->param0 = data;
	p->param = p->param0;
	p->bound = 1;

	return SQL_SUCCESS;	
}

/*
 Internal query execution used by SQLExecute() and SQLExecDirect().
 @param stmt statement handle
 @param initial false when called from SQLPutData()
 @result ODBC error code
*/
extern SQLRETURN op_execute(SQLHSTMT stmt, int initial)
{
	stmt_t *s;
	dbc_t *d;
	char *errp = NULL;
	SQLRETURN ret = FALSE;

	if (stmt == SQL_NULL_HSTMT) {
		return SQL_INVALID_HANDLE;
	}

	s = (stmt_t *)stmt;
	if (s->dbc == SQL_NULL_HDBC) {
noconn:
		set_stat_stmt (s, -1, "not connected", "HY000");
		return SQL_ERROR;
	}

	d = (dbc_t *)s->dbc;
	if (!d->db_name) {
		goto noconn;
	}

	if (!s->query) {
		set_stat_stmt(s, -1, "no query prepared", "HY000");
		return SQL_ERROR;
	}

	do_query(s);

	return ret;
}

/*
 Reset bound columns to unbound state.
 @param s statement pointer
*/
static void unbindcols(stmt_t *s)
{
	int i;

	s->bkmrkcol.type = -1;
	s->bkmrkcol.max = 0;
	s->bkmrkcol.lenp = NULL;
	s->bkmrkcol.valp = NULL;
	s->bkmrkcol.index = 0;
	s->bkmrkcol.offs = 0;

	for (i = 0; s->bindcols && i < s->nbindcols; i++) 
	{
		s->bindcols[i].type = -1;
		s->bindcols[i].max = 0;
		s->bindcols[i].lenp = NULL;
		s->bindcols[i].valp = NULL;
		s->bindcols[i].index = i;
		s->bindcols[i].offs = 0;
	}
}

static void do_query(stmt_t *s)
{
	return ;
}